/**
 * @file mcast_socket.cpp
 * @brief 多播套接字实现
 * @details 实现多播套接字的初始化、加入/离开多播组以及数据发送和接收功能
 */

#include "mcast_socket.h"

namespace Common {
  /**
   * @namespace Common
   * @brief 包含常用工具类和函数的命名空间
   */
  /**
   * @brief 初始化多播套接字
   * @param ip 多播组IP地址
   * @param iface 网络接口名称
   * @param port 端口号
   * @param is_listening 是否以监听模式初始化
   * @return 初始化的套接字文件描述符，失败时返回-1
   * @details 初始化多播套接字以从流中读取或发布到流中，注意这不会加入多播组
   */
  auto McastSocket::init(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int {
    // 创建套接字配置，指定使用UDP（多播基于UDP）
    const SocketCfg socket_cfg{ip, iface, port, true, is_listening, false};
    // 使用socket_utils.h中的createSocket函数创建套接字
    socket_fd_ = createSocket(logger_, socket_cfg);
    return socket_fd_;
  }

  /**
   * @brief 加入多播组
   * @param ip 要加入的多播组IP地址
   * @return 加入成功返回true，失败返回false
   * @details 添加/加入成员资格/订阅多播流
   */
  bool McastSocket::join(const std::string &ip) {
    // 调用socket_utils.h中的join函数加入多播组
    return Common::join(socket_fd_, ip);
  }

  /**
   * @brief 离开多播组
   * @param ip 要离开的多播组IP地址（当前未使用）
   * @param port 端口号（当前未使用）
   * @details 移除/离开成员资格/取消订阅多播流，关闭套接字
   */
  auto McastSocket::leave(const std::string &, int) -> void {
    // 关闭套接字
    close(socket_fd_);
    // 将套接字文件描述符设置为无效值
    socket_fd_ = -1;
  }

  /**
   * @brief 发送和接收数据
   * @return 如果有数据被接收则返回true，否则返回false
   * @details 非阻塞方式发布外发数据并读取传入数据
   */
  auto McastSocket::sendAndRecv() noexcept -> bool {
    // 非阻塞方式读取数据，如果有数据可用则分发回调
    const ssize_t n_rcv = recv(socket_fd_, inbound_data_.data() + next_rcv_valid_index_, McastBufferSize - next_rcv_valid_index_, MSG_DONTWAIT);
    if (n_rcv > 0) {
      // 更新下一个有效的接收索引
      next_rcv_valid_index_ += n_rcv;
      // 记录读取操作的日志
      logger_.log("%:% %() % read socket:% len:%\n", __FILE__, __LINE__, __FUNCTION__, Common::getCurrentTimeStr(&time_str_), socket_fd_,
                  next_rcv_valid_index_);
      // 调用接收回调函数处理数据
      recv_callback_(this);
    }

    // 将发送缓冲区中的数据发布到多播流
    if (next_send_valid_index_ > 0) {
      // 非阻塞方式发送数据，不生成SIGPIPE信号
      ssize_t n = ::send(socket_fd_, outbound_data_.data(), next_send_valid_index_, MSG_DONTWAIT | MSG_NOSIGNAL);

      // 记录发送操作的日志
      logger_.log("%:% %() % send socket:% len:%\n", __FILE__, __LINE__, __FUNCTION__, Common::getCurrentTimeStr(&time_str_), socket_fd_, n);
    }
    // 重置发送索引，准备下一次发送
    next_send_valid_index_ = 0;

    // 如果有数据被接收则返回true
    return (n_rcv > 0);
  }

  /**
   * @brief 将数据复制到发送缓冲区
   * @param data 要发送的数据指针
   * @param len 数据长度
   * @details 将数据复制到发送缓冲区，但不立即发送，需要调用sendAndRecv()才会实际发送
   */
  auto McastSocket::send(const void *data, size_t len) noexcept -> void {
    // 将数据复制到发送缓冲区的当前位置
    memcpy(outbound_data_.data() + next_send_valid_index_, data, len);
    // 更新下一个有效的发送索引
    next_send_valid_index_ += len;
    // 确保缓冲区没有溢出，如果溢出则表示没有及时调用sendAndRecv()
    ASSERT(next_send_valid_index_ < McastBufferSize, "Mcast socket buffer filled up and sendAndRecv() not called.");
  }
}
